/* Copyright (C) 2011 Riccardo Marson

   This file is part of OpenPool, an open-source game of pool.

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included
   in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
   RICCARDO MARSON BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
   AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
   CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

  See Readme.rtf for further information.

*/

#include "SFML/Config.hpp"
#include "Rapidxml/rapidxml.hpp"

#include "scriptmanager.h"
#include "logmanager.h"
#include "rescache.h"

#include "as_addon/scriptstdstring.h"
#include "as_addon/scriptarray.h"
#include "as_addon/scriptdictionary.h"

#include "script/gameoptions.h"
#include "script/gametypes.h"

#include <assert.h>
#include <sstream>
#include <ctime>

// leva
#include "globjects.h"

using namespace std;

CScriptManager* CScriptManager::uniqueInstance = NULL;

CScriptManager::CScriptManager()
{
    assert(!uniqueInstance);
    uniqueInstance = this;

    mScriptEngine = NULL;
    mActiveQueue = 0;
    mMaxProcessingTime = 0.01; //10 milli-seconds.

}

CScriptManager::~CScriptManager()
{
    assert(uniqueInstance);
    uniqueInstance = NULL;

    CLogManager::getSingleton()->logMessage( "Scripting Engine Shutting Down." );

    // AS cleanup
    asIScriptContext* context;

    while( !mContextStack.empty() )
    {
    	context = *(mContextStack.begin());
    	mContextStack.pop_front();
    	context->Release();
    }

    if (mScriptEngine != NULL)
    	mScriptEngine->Release();

}

CScriptManager* CScriptManager::getSingleton()
{
    return uniqueInstance;
}

void CScriptManager::init()
{
    CLogManager::getSingleton()->logMessage("Initializing Scripting Engine...");

	int r;

	//initialize the script engine
	mScriptEngine = asCreateScriptEngine(ANGELSCRIPT_VERSION);

	assert(mScriptEngine);

	//register script message callback as a method from "this" instance of CScriptManager
	r = mScriptEngine->SetMessageCallback(asMETHOD( CScriptManager,ScriptMessageCallback), this, asCALL_THISCALL );
	// make sure there are no errors
	assert (r >= 0);

	//-------------------------
	//AS add-ons

	// Registers std::string as a AS type for the scripts to use.
	RegisterStdString(mScriptEngine);
	mRegisteredTypeIDs["string"] = mScriptEngine->GetTypeIdByDecl("string");

	// array
	RegisterScriptArray(mScriptEngine, true);

	// dictionary: a map with the key set to std::string and the value to any object.
	// allows storing different types in a single map.
	RegisterScriptDictionary(mScriptEngine);
	mRegisteredTypeIDs["dictionary"] = mScriptEngine->GetTypeIdByDecl("dictionary");

	//-------------------------

    // register the log manager and its logMessage function so that the scripts will be able to sent notifications to the host applications
    // the script engine does not need to know the size nor the behaviour of this class
    // since it will never instance it, it just references an existing instance managed by the host.
    r = mScriptEngine->RegisterObjectType("CLogManager", 0, asOBJ_REF | asOBJ_NOHANDLE);

    assert(r >= 0);

    // the only method that the engine needs to be aware of is the logMessage, in its std::string variant (it doesn't know char*!).
    r = mScriptEngine->RegisterObjectMethod("CLogManager", "void logMessage(const string &in)", asMETHODPR(CLogManager, logMessage, (const string&), void ), asCALL_THISCALL );

    assert(r  >= 0);

    // register a reference to the (unique) instance of CLogManager that lives in the host application.
    r = mScriptEngine->RegisterGlobalProperty("CLogManager logMng", CLogManager::getSingleton());

    assert(r >= 0);

    mRegisteredTypeIDs["CLogManager"] = mScriptEngine->GetTypeIdByDecl("CLogManager");

	// Register all types needed
	RegisterGameOptions(mScriptEngine, mRegisteredTypeIDs);

	RegisterVector(mScriptEngine, mRegisteredTypeIDs);

	RegisterGameTypes(mScriptEngine, mRegisteredTypeIDs);

}

bool CScriptManager::configure()
{
	char *xml_src = NULL;

	// load and compile all script modules
	if (!beginModule("MainWindow"))
		return false;

	xml_src = NULL;

	xml_src = CResourceCache::getSingletonPtr()->requestScriptSource("manifest.xml");

	if (xml_src == NULL)
		return false;

	rapidxml::xml_document<> manifest;
	rapidxml::xml_node<>* root_node;

	manifest.parse<0> (const_cast<char *>(xml_src));

	root_node = manifest.first_node();

	if ( string( root_node->name() ) != "openpool" )
		return false;

	ProcessXmlNode(root_node);

	delete xml_src;

	// load and compile all script modules

	for ( ScriptModuleIt m_it = mModuleList.begin(); m_it != mModuleList.end(); ++m_it )
	{
		cout << (*m_it).first << endl;

		if (!beginModule( (*m_it).first ) )
			return false;

		for ( ScriptModuleSourceIt s_it = (*m_it).second.begin(); s_it != (*m_it).second.end(); ++s_it  )
		{
			cout << (*s_it).second << " " << (*s_it).first  << endl;
			if (!addSource( (*s_it).second, (*s_it).first ) )
				return false;
 		}

		if (!buildModule())
			return false;
	}

	return true;

}

void CScriptManager::ProcessXmlNode(rapidxml::xml_node<> *node)
{
	rapidxml::xml_node<> *cur_node;
	rapidxml::xml_node<> *child_node;

	string nodeName, nodeValue, attribName, attribValue;

	for ( cur_node = node->first_node(); cur_node; cur_node = cur_node->next_sibling() )
	{
	    nodeName = cur_node->name();

	    if ( nodeName == "module" )
	    {
	    	ScriptModuleSource srclist;

	    	attribName = cur_node->first_attribute()->name();
	    	attribValue = cur_node->first_attribute()->value();

	    	if (attribName == "name")
	    		mModuleList[attribValue] = srclist;
	    }
	    else if ( nodeName == "script" )
	    {
	    	string moduleName = cur_node->parent()->first_attribute()->value();
	    	ScriptModuleIt m_it;

	    	attribName = cur_node->first_attribute()->name();
	    	attribValue = cur_node->first_attribute()->value();
	    	nodeValue = cur_node->value();

	    	m_it = mModuleList.find(moduleName);

	    	if (m_it != mModuleList.end())
	    	{
	    		pair< string, string > srcFile;
	    		srcFile.first = attribValue;
	    		srcFile.second = nodeValue;

	    		(*m_it).second.push_back( srcFile );
	    	}
	    }

	    // if the node has children, and they are proper node elements (not values, comments or other), process them before moving on.
	    child_node = cur_node->first_node();
	    if ( ( child_node ) && ( child_node->type() == rapidxml::node_element ))
	    	ProcessXmlNode( cur_node );
	}

}

bool CScriptManager::registerGlobalProperty(const char* declaration, void *pointer)
{
    int r;

    r = mScriptEngine->RegisterGlobalProperty(declaration, pointer);

    if ( r >= 0)
        return true;
    else
        return false;
}

int CScriptManager::GetASTypeID(std::string decl)
{
	map<string, int>::iterator it = mRegisteredTypeIDs.find(decl);

	if (it != mRegisteredTypeIDs.end())
		return (*it).second;
	else
		return 0;
}

bool CScriptManager::executeFunction(std::string moduleName, std::string funcDecl)
{
	asIScriptModule *module = mScriptEngine->GetModule(moduleName.c_str());
	asIScriptFunction *func = module->GetFunctionByDecl(funcDecl.c_str());

	if (func == 0)
	{
		return false;
	}

	bool ret = true;
	int r;

	asIScriptContext* context = getAvailableContext(func);

	execute(context);

	freeActiveContext(context);

	return ret;
}

bool CScriptManager::executeFunction(std::string moduleName, std::string funcDecl, int argNumber, void **args)
{
	asIScriptModule *module = mScriptEngine->GetModule(moduleName.c_str());
	asIScriptFunction *func = module->GetFunctionByDecl(funcDecl.c_str());

	if (func == 0)
	{
		return false;
	}

	bool ret = true;
	int r;

	asIScriptContext* context = getAvailableContext(func);

	for (int i = 0; i < argNumber; ++i)
	{
		context->SetArgObject(i, args[i]);
	}

	execute(context);

	freeActiveContext(context);

	return ret;
}

bool CScriptManager::executeFunction(asIScriptFunction* func)
{
	if (func == NULL)
	{
		return false;
	}

	bool ret = true;
	int r;

	asIScriptContext* context = getAvailableContext(func);

	execute(context);

	freeActiveContext(context);

	return ret;
}

bool CScriptManager::queueFunctionExecution(std::string moduleName, std::string funcDecl)
{
	asIScriptModule *module = mScriptEngine->GetModule(moduleName.c_str());
	asIScriptFunction *func = module->GetFunctionByDecl(funcDecl.c_str());

	if (func == NULL)
	{
		return false;
	}

	// store in the inactive queue
	unsigned short backQueue = whichQueue();

	mPendingFunctions[backQueue].push_back(func);

	return true;

}

bool CScriptManager::queueFunctionExecution(asIScriptFunction* func)
{
	if (func == NULL)
	{
		return false;
	}

	// store in the inactive queue
	unsigned short backQueue = whichQueue();

	mPendingFunctions[backQueue].push_back(func);

	return true;
}

void CScriptManager::processQueuedFunctions()
{
	ExecQueue & activeQueue = mPendingFunctions[mActiveQueue];

	// exit now if there are no pending functions
	if (activeQueue.empty())
	{
		swapQueues();
		return;
	}

	clock_t startTime, time;
	float dt = 0.0; //elapsed time in seconds.

	startTime = clock();

	// TODO: since the execution time for each function is unknown and realistically
	// will vary wildly between functions, it might be necessary to pause a function call
	// if the time-out has passed and resume it the next time around.
	while (dt <= mMaxProcessingTime)
	{
		ExecQueue::iterator qf;
		qf = activeQueue.begin();

		if (qf == activeQueue.end())
			break;

		int r;

		asIScriptFunction* func = *qf;

		activeQueue.pop_front();

		asIScriptContext* context = getAvailableContext(func);

		execute(context);

		freeActiveContext(context);

		time = clock();
		dt = (time - startTime)/(float)CLOCKS_PER_SEC;
	}

	if (activeQueue.empty())
		swapQueues();
}

bool CScriptManager::beginModule(string moduleName)
{
	int r;

	// load and compile all script modules
	r = mScriptBuilder.StartNewModule(mScriptEngine, moduleName.c_str());

	if (r < 0)
	{
		return false;
	}
	else
		return true;
}

bool CScriptManager::addSource(string srcFile, string sectionName)
{
	int r;

	char *src;

	src = CResourceCache::getSingletonPtr()->requestScriptSource(srcFile);

	if (src != NULL)
		r = mScriptBuilder.AddSectionFromMemory(src, sectionName.c_str());

	delete src;

	if (r < 0)
		return false;
	else
		return true;
}

bool CScriptManager::buildModule()
{
	int r;

	r = mScriptBuilder.BuildModule();

	if (r < 0)
		return false;
	else
		return true;
}

bool CScriptManager::execute(asIScriptContext* context)
{
	int r;

	r = context->Execute();

	if ( r != asEXECUTION_FINISHED )
	{
		if (r == asEXECUTION_EXCEPTION )
		{
			stringstream msgstream(ios::in | ios::out);

			msgstream << context->GetExceptionString() <<  " In Function: "  << mScriptEngine->GetFunctionById(context->GetExceptionFunction())->GetDeclaration()
			<< " @ Line: " << context->GetExceptionLineNumber();


			CLogManager::getSingleton()->logMessage(msgstream.str());
			return false;
		}

		return false;
	}

	return true;
}

void CScriptManager::ScriptMessageCallback(const asSMessageInfo *scriptMsg, void *param)
{
	string type;

	if ( scriptMsg->type == asMSGTYPE_WARNING )
		type = "WARN";
	else if ( scriptMsg->type == asMSGTYPE_INFORMATION )
		type = "INFO";
    else if ( scriptMsg->type == asMSGTYPE_ERROR )
        type = "ERR";

	stringstream ss(ios::in | ios::out);

	ss << "AS: " << scriptMsg->section << " ( row: " << scriptMsg->row << ", col: " << scriptMsg->col << " ) : " << type << " : " <<
			scriptMsg->message << endl;

	CLogManager::getSingleton()->logMessage( ss.str().c_str() );
}

asIScriptContext* CScriptManager::getAvailableContext(asIScriptFunction* func)
{
	asIScriptContext* context = NULL;

	if ( mContextStack.empty() )
	{
		context = mScriptEngine->CreateContext();
	}
	else
	{
		ContextStack::iterator con_it = mContextStack.begin();
		mContextStack.pop_front();

		context = *con_it;
	}

	// extra precaution: check that the pointer has been assigned.
	if (context != NULL)
		context->Prepare(func);

	return context;

}

void CScriptManager::freeActiveContext(asIScriptContext* context)
{
	context->Unprepare();

	mContextStack.push_back(context);
}


